/*
  Meanwhile - Unofficial Lotus Sametime client library
  
  Copyright (c) 2007 Christopher O'Brien <siege@preoccupied.net>
  
  License: LGPL
  
  This library is free software; you can redistribute it and/or modify
  it under the terms of version 2 the GNU Library General Public
  License as published by the Free Software Foundation.
                                                                                
  This library is distributed in the hope that it will be useful, but
  WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Library General Public License for more details.
  
  You should have received a copy of the GNU Library General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  USA
*/


#include "mw-message-buffer.h"
#include <glib.h>
#include <strings.h>


struct mw_message_buffer {
  enum {
    BUFFER_STATE_TRIM = 0,
    BUFFER_STATE_LENGTH = 1,
    BUFFER_STATE_DATA = 2,
  } state;

  guchar *buf;
  gsize msg;
  gsize use;

  MwMessageBufferCallback callback;

  gpointer user;
  GDestroyNotify user_clear;

  guchar lenbuf[4];
};


static void switch_to_length(MwMessageBuffer *mb) {
  mb->state = BUFFER_STATE_LENGTH;

  mb->buf = mb->lenbuf;
  mb->msg = 4;
  mb->use = 0;
}


static void switch_to_data(MwMessageBuffer *mb, gsize len) {
  mb->state = BUFFER_STATE_DATA;

  mb->buf = g_malloc(len);
  mb->msg = len;
  mb->use = 0;
}


/** skip past leading counter and keepalive bytes. next state is
    BUFFER_STATE_LENGTH */
static void buffer_feed_trim(MwMessageBuffer *mb,
			     guchar **buf, gsize *len) {
  guchar *b = *buf;
  gsize l = *len;

  while(l && *b & 0x80) {
    b++; l--;
  }

  if(l) {
    switch_to_length(mb);
  }

  *buf = b;
  *len = l
}


/** attempts to fill the mb's buffer from buf. returns TRUE if mb is
    filled (and thus ready to proceed to the next state */
static gboolean buffer_fill(MwMessageBuffer *mb,
			    guchar **buf, gsize *len) {
  gsize needed, l = *len;
  
  needed = mb->msg - mb->use;
  if(needed < l) {
    l = needed;
  }

  memcpy(mb->buf + mb->use, *buf, l);
  mb->use += l;

  *buf += l;
  *len -= l;

  return (mb->msg == mb->use);
}


static guint32 uint32_read(guchar *b) {
  guint32 val;
  
  val = (*b++ & 0xff) << 24;
  val = val | (*b++ & 0xff) << 16;
  val = val | (*b++ & 0xff) << 8;
  val = val | (*b++ & 0xff);

  return val;
}


static void buffer_feed_length(MwMessageBuffer *mb,
			       guchar **buf, gsize *len) {

  if(buffer_full(mb, buf, len)) {
    switch_to_data(mb, uint32_read(mb->buf));
  }
}


static void buffer_feed_data(MwMessageBuffer *mb,
			     guchar **buf, gsize *len) {

  if(buffer_fill(mb, buf, len)) {
    
    if(mb->callback) {
      mb->callback(mb, mb->buf, mb->msg, mb->user);
    }

    mw_message_buffer_reset(mb);
  }
}


MwMessageBuffer *mw_message_buffer_new(MwMessageBufferCallback callback,
				       gpointer user_data,
				       GDestroyNotify user_clear) {
  MwMessageBuffer *msgb;

  msgb = g_new0(MwMessageBuffer, 1);
  msgb->callback = callback;
  msgb->user = user_data;
  msgb->user_clear = user_clear;

  return msgb;
}


void mw_message_buffer_feed(MwMessageBuffer *msgbuf,
			    const guchar *data, gsize len) {

  g_return_if_fail(msgbuf != NULL);
  g_return_if_fail(data != NULL || len == 0);

  while(len > 0) {
    switch(msgbuf->state) {
    case BUFFER_STATE_TRIM:
      buffer_feed_trim(msgbuf, (guchar **) &buf, &len);
      break;

    case BUFFER_STATE_LENGTH:
      buffer_feed_length(msgbuf, (guchar **) &buf, &len);
      break;

    case BUFFER_STATE_DATA:
      buffer_feed_data(msgbuf, (guchar **) &buf, &len);
      break;

    default:
      g_assert_not_reached();
    }
  }
}


void mw_message_buffer_reset(MwMessageBuffer *msgbuf) {
  g_return_if_fail(msgbuf != NULL);
  
  if(msgbuf->state == BUFFER_STATE_DATA) {
    g_free(msgbuf->buf);
  }

  msgbuf->state = BUFFER_STATE_TRIM;
  msgbuf->buf = NULL;
  msgbuf->msg = 0;
  msgbuf->use = 0;
}


void mw_message_buffer_free(MwMessageBuffer *msgbuf) {
  if(msgbuf) {
    mw_message_buffer_reset(msgbuf);
    if(msgbuf->user_clear) {
      msgbuf->user_clear(msgbuf->user);
    }
    g_free(msgbuf);
  }
}


/* The end. */
